package proxyPool

import (
	"container/list"
	"context"
	"gitee.com/kaylee595/gkaylee/kerr"
	"github.com/imroc/req/v3"
	"math/rand/v2"
	"net/http"
	"sync"
)

type SelectMode int

const (
	SelectModeReuse  SelectMode = iota // 复用, 只要前面节点未失效, 就不使用后面的节点
	SelectModePoll                     // 轮询节点列表
	SelectModeRandom                   // 随机选择一个节点
)

// Pool 决定当代理源返回多个节点时, 如何选择节点, 例如: 轮询, 复用优先, 随机.
type Pool struct {
	source Source
	nodes  *list.List
	mu     sync.RWMutex
	sm     SelectMode
	cur    *list.Element
}

type Option func(*Pool)

// WithSelectMode 调整节点选择模式, 默认: SelectModeReuse
func WithSelectMode(m SelectMode) Option {
	return func(p *Pool) { p.sm = m }
}

func NewPool(s Source, opts ...Option) *Pool {
	p := &Pool{source: s, nodes: list.New()}
	for _, opt := range opts {
		opt(p)
	}
	return p
}

var randIntN = rand.IntN

// GetProxyNode 获取一个有效的代理节点
func (p *Pool) GetProxyNode(ctx context.Context) (Node, error) {
	switch p.sm {
	case SelectModeReuse:
		for {
			p.mu.RLock()
			e := p.nodes.Front()
			p.mu.RUnlock()
			if e == nil { // 没有节点则从来源加载
				p.mu.Lock()
				err := p.load(ctx)
				p.mu.Unlock()
				if err != nil {
					return nil, kerr.WrapTraceStack(err)
				}
				continue
			}
			n := e.Value.(Node)
			if n.IsValid() {
				n.Use()
				return n, nil
			}
			p.mu.Lock()
			p.nodes.Remove(e)
			p.mu.Unlock()
		}
	case SelectModePoll:
		p.mu.Lock()
		defer p.mu.Unlock()
		for {
			if p.cur == nil {
				e := p.nodes.Front()
				if e == nil { // 没有节点则从来源加载
					err := p.load(ctx)
					if err != nil {
						return nil, kerr.WrapTraceStack(err)
					}
					continue
				}
				n := e.Value.(Node)
				if n.IsValid() {
					n.Use()
					p.cur = e
					return n, nil
				}
				p.nodes.Remove(e)
			} else {
				e := p.cur.Next()
				if e == nil { // 抵达列表尾部
					p.cur = nil
					continue
				}
				n := e.Value.(Node)
				if n.IsValid() {
					n.Use()
					p.cur = e
					return n, nil
				}
				p.nodes.Remove(e)
			}
		}
	case SelectModeRandom:
		for {
			var idx int
			p.mu.RLock()
			e := p.nodes.Front()
			if e != nil && p.nodes.Len() > 1 { // 存在其他元素的情况下, 随机指向元素
				idx = randIntN(p.nodes.Len())
				for i := 0; i < idx; i++ {
					e = e.Next()
				}
			}
			p.mu.RUnlock()
			if e == nil { // 没有节点则从来源加载
				p.mu.Lock()
				err := p.load(ctx)
				p.mu.Unlock()
				if err != nil {
					return nil, kerr.WrapTraceStack(err)
				}
				continue
			}

			n := e.Value.(Node)
			if n.IsValid() {
				n.Use()
				return n, nil
			}
			p.mu.Lock()
			p.nodes.Remove(e)
			p.mu.Unlock()
		}
	default:
		return nil, kerr.WrapTraceStack(ErrUnknowSelectMode)
	}
}

// load 加载IP进列表中
func (p *Pool) load(ctx context.Context) error {
	if p.nodes.Len() > 0 {
		return nil
	}
	nodes, err := p.source.Fetch(ctx)
	if err != nil {
		return kerr.WrapTraceStack(err)
	}
	if len(nodes) == 0 {
		return kerr.New("加载IP失败")
	}
	for _, node := range nodes {
		p.nodes.PushBack(node)
	}
	return nil
}

// GetClient 获取一个HTTPClient, 它将通过 GetProxyNode 设置一个代理.
// 如果你的需求是一直更换不同的代理去请求你的目标站点, 则需要通过不同的HTTPClient去请求, 而不是复用Client.
// 在持续并发的过程中, 可能会需要不断的更换HTTPClient, 你应该在每个HTTPClient不再被使用时, 调用 CloseIdleConnections 释放空闲连接.
func (p *Pool) GetClient(ctx context.Context) (*req.Client, error) {
	proxyNode, err := p.GetProxyNode(ctx)
	if err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	return req.C().SetProxy(http.ProxyURL(proxyNode.Url())), nil
}
